/**
 * i am version 15
 */

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
#include"queue/bqueue.h"
#include"process/process.h"
#include"process/instruction.h"
#include"scheduler/scheduler.h"
#include"arraylist/array_list.h"
#include<unistd.h>
#include<termios.h>

#define MEM_LOCATIONS 30
#define MEM_PRO_CON_START 20
#define MAX_PRO_CONS

void check_mem_queues();
void* interrupting_cow(void*);
void* kb_loop(void*);
void* joy_loop(void*);

void handle_interrupt(int);


bqueue ready_queue;
bqueue kb_queue;
bqueue joy_queue;

pthread_t kb_thread;
pthread_t joy_thread;
pthread_t system_timer;


int done = 0;

int shared_mem[MEM_LOCATIONS];
/**
 * OS: 0-3
 * CALC: 4-7
 * PROD/CONS: 20-29
 */
queue mem_queue[MEM_LOCATIONS];

Scheduler* sched;

int interrupted = 0;

int main(int argc, char** argv){
  Process* current_proc;
  Instruction current_inst;
  int current_pro_con = MEM_PRO_CON_START;
  int i = 0;
  int blocked = 0;

  sched = (Scheduler*)malloc(sizeof(Scheduler));

  for(i=0;i<MEM_LOCATIONS;i++){
    queue_init(&mem_queue[i], 1);
  }

  //figure out what scheduler to make
  if(argc == 1){ //check to make sure there is some input
    printf("why you no put in arguments arguments good......\n");
    printf("Starting with 1 PRO/CONS 1 ui 1 calc using priority scheduling \n");
    schedulerInit(sched, PRIORITY);

    sched->ready_process(sched, createUI());
    sched->ready_process(sched, createCalc());
    sched->ready_process(sched, createCons(20));
    sched->ready_process(sched, createProd(20));

  }else{  //there was some input
    //switch on the first arg.
    switch(argv[1][0]){
    case 'p':
      schedulerInit(sched, PRIORITY);
      printf("Started Priority scheduling.\n");
      break;
    case 'l':
      schedulerInit(sched, LOTTERY);
      printf("Started Lottery scheduling\n");
      break;
    case 'r':
      schedulerInit(sched, ROUND_ROBIN);
      printf("Started Round Robin scheduling\n");
      break;
    }

      for(i = 0; i < strlen(argv[2]);i++){
        switch(argv[2][i]){
        case 'u':
          sched->ready_process(sched, createUI());
          printf("Made a ui process\n");
          break;
        case 'c':
          sched->ready_process(sched, createCalc());
          printf("Made a Calc process\n");
          break;
        case 'p':
          sched->ready_process(sched, createProd(current_pro_con));
          sched->ready_process(sched, createCons(current_pro_con++));
          printf("Made a Producer and a Consumer \n");
          break;
       }
   }

  }






  bqueue_init(&kb_queue);
  bqueue_init(&joy_queue);

  pthread_create(&kb_thread, NULL, &kb_loop, NULL);
  pthread_create(&joy_thread, NULL, &joy_loop, NULL);
  pthread_create(&system_timer, NULL, &interrupting_cow, NULL);

  while(!done){
    current_proc = sched->next_proc(sched);
    current_proc->s = RUNNING;
    printf("\n---------------------------------------------------------");
    printf("\nRunning Process: %s", procTypeToString(current_proc->t));
    fflush(stdout);

    while(!interrupted && !blocked){
      current_inst = procNextInstruction(current_proc);

      switch(current_inst.type){
      case IO_REQ:
        switch(current_inst.arg){
          case KEYBOARD:
            current_proc->s = BLOCKED;
            printf("\nProcess Blocking for KeyBoard IO: %s", procTypeToString(current_proc->t));
            bqueue_enqueue(&kb_queue, current_proc);
            blocked = 1;
            break;
          case JOYSTICK:
            current_proc->s = BLOCKED;
            printf("\nProcess Blocking for Joystick IO: %s", procTypeToString(current_proc->t));
            bqueue_enqueue(&joy_queue, current_proc);
            blocked = 1;
            break;
        }
        break;
      case MEM_UP:
        printf("\nProcess Upped MEM ADDR (%d): %s", current_inst.arg, procTypeToString(current_proc->t));
        shared_mem[current_inst.arg]++;
        break;
      case MEM_DN:

        if(shared_mem[current_inst.arg] <= 0){
          //Nothing to down, block until there is.
          current_proc->s = BLOCKED;
          printf("\nProcess Blocking for MEM ADDR (%d): %s", current_inst.arg, procTypeToString(current_proc->t));
          queue_enqueue(&mem_queue[current_inst.arg], current_proc);

          blocked = 1;

        } else {
          //do the down.
          shared_mem[current_inst.arg]--;
        }

        break;
      case DUMB_INST:
        //printf("\nProcess did nothing: %s", procTypeToString(current_proc->t));
        break;
      }
      sleep(1);
    }

    if(blocked == 1){
      blocked = 0;
    } else {
      interrupted = 0;
      current_proc->s = RUNNABLE;
      sched->ready_process(sched, current_proc);
      printf("\nProcess interrupted: %s", procTypeToString(current_proc->t));
    }

    check_mem_queues();
  }


  return EXIT_SUCCESS;
}

void check_mem_queues(){
  int i = 0;
  Process* temp;

  for(i=0;i<MEM_LOCATIONS;i++){
    if(shared_mem[i] > 0){

      temp = (Process*)queue_dequeue(&mem_queue[i]);
      if(temp != NULL){

        sched->ready_process(sched, temp);
        printf("\nSet process to Ready: %s", procTypeToString(temp->t));
        shared_mem[i]--;
      }
    }
  }
}

/**
 * The system Timer.
 */
void* interrupting_cow(void* arg){
  while(!done){
    sleep(5);
    moo();
  }
}

void moo(){
  interrupted = 1;
}

void* kb_loop(void* arg){
  char c = 0;
  //make this stop when I change a var.
  while(!done){
    c= getch();
    if(c == '.'){
      done = 1;
    }
    printf("\nReceived interrupt from keyboard.");
    handle_interrupt(KEYBOARD);
  }
}

void* joy_loop(void* arg){
  while(!done){
    sleep(10);
    printf("\nReceived interrupt from joystick.");
    handle_interrupt(JOYSTICK);
  }
}
void handle_interrupt(int the_source){
  Process* temp;
  switch(the_source){
  case KEYBOARD:
    temp = (Process*)bqueue_dequeue(&kb_queue);
    if(temp != NULL)
      printf("About to ready from KB: %s", procTypeToString(temp->t));
    break;
  case JOYSTICK:
    temp = (Process*)bqueue_dequeue(&joy_queue);
    if(temp != NULL)
      printf("About to ready from JOY: %s", procTypeToString(temp->t));
    break;
  }
  if(temp != NULL){

    sched->ready_process(sched, temp);
  }
}

int getch() {
    struct termios term_attr;
    tcflag_t old_c_lflag;
    int c;

    tcgetattr(STDIN_FILENO, &term_attr);
    old_c_lflag = term_attr.c_lflag;
    term_attr.c_lflag &= ~(ICANON|ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &term_attr);
    c = getchar();
    term_attr.c_lflag = old_c_lflag;
    tcsetattr(STDIN_FILENO, TCSANOW, &term_attr);
    return c;
}
